home *** CD-ROM | disk | FTP | other *** search
- /* SCCS Id: @(#)winmesg.c 3.1 92/05/19 */
- /* Copyright (c) Dean Luick, 1992 */
- /* NetHack may be freely redistributed. See license for details. */
-
- /*
- * Message window routines.
- *
- * Global functions:
- * set_message_height()
- * create_message_window()
- * destroy_message_window()
- * display_message_window()
- * append_message()
- */
- #include <X11/Intrinsic.h>
- #include <X11/StringDefs.h>
- #include <X11/Shell.h>
- #include <X11/Xaw/Cardinals.h>
- #include <X11/Xaw/Viewport.h>
- #include "Window.h" /* Window widget declarations */
-
- #include "hack.h"
- #include "winX.h"
-
- static const char message_translations[] =
- "#override\n\
- <Key>: no-op()";
-
- static struct line_element *get_previous();
- static void set_circle_buf();
- static char *split();
- static void add_line();
- static void redraw_message_window();
- static void mesg_check_size_change();
- static void mesg_exposed();
- static void get_gc();
- static void mesg_resized();
-
- /* Adjust the number of rows on the given message window. */
- void
- set_message_height(wp, rows)
- struct xwindow *wp;
- Dimension rows;
- {
- Arg args[1];
-
- wp->pixel_height = wp->mesg_information->char_height * rows;
-
- XtSetArg(args[0], XtNheight, wp->pixel_height);
- XtSetValues(wp->w, args, ONE);
- }
-
- /* Move the message window's vertical scrollbar's slider to the bottom. */
- void
- set_message_slider(wp)
- struct xwindow *wp;
- {
- Widget scrollbar;
- float top;
-
- scrollbar = XtNameToWidget(XtParent(wp->w), "vertical");
-
- if (scrollbar) {
- top = 1.0;
- XtCallCallbacks(scrollbar, XtNjumpProc, &top);
- }
- }
-
-
- void
- create_message_window(wp, create_popup, parent)
- struct xwindow *wp; /* window pointer */
- boolean create_popup;
- Widget parent;
- {
- Arg args[8];
- Cardinal num_args;
- Widget viewport;
- struct mesg_info_t *mesg_info;
-
- wp->type = NHW_MESSAGE;
-
- wp->mesg_information = mesg_info =
- (struct mesg_info_t *) alloc(sizeof(struct mesg_info_t));
-
- mesg_info->fs = 0;
- mesg_info->num_lines = 0;
- mesg_info->head = mesg_info->last_pause =
- mesg_info->last_pause_head = (struct line_element *) 0;
- mesg_info->dirty = False;
- mesg_info->viewport_width = mesg_info->viewport_height = 0;
-
- /*
- * We should have an .Xdefaults option that specifies the number of lines
- * to be displayed. Until then, we'll use DEFAULT_LINES_DISPLAYED.
- * E.g.:
- *
- * if (a lines value from .Xdefaults exists)
- * lines_displayed = lines value from .Xdefaults;
- * else
- * lines_displayed = DEFAULT_LINES_DISPLAYED;
- */
- if (flags.msg_history < DEFAULT_LINES_DISPLAYED)
- flags.msg_history = DEFAULT_LINES_DISPLAYED;
- if (flags.msg_history > MAX_HISTORY) /* a sanity check */
- flags.msg_history = MAX_HISTORY;
-
- set_circle_buf(mesg_info, (int) flags.msg_history);
-
- /* Create a popup that becomes the parent. */
- if (create_popup) {
- num_args = 0;
- XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
-
- wp->popup = parent = XtCreatePopupShell("message_popup",
- topLevelShellWidgetClass,
- toplevel, args, num_args);
- }
-
- /*
- * Create the viewport. We only want the vertical scroll bar ever to be
- * visible. If we allow the horizontal scrollbar to be visible it will
- * always be visible, due to the stupid way the Athena viewport operates.
- */
- num_args = 0;
- XtSetArg(args[num_args], XtNallowVert, True); num_args++;
- viewport = XtCreateManagedWidget(
- "mesg_viewport", /* name */
- viewportWidgetClass, /* widget class from Window.h */
- parent, /* parent widget */
- args, /* set some values */
- num_args); /* number of values to set */
-
- /*
- * Create a message window. We will change the width and height once
- * we know what font we are using.
- */
- num_args = 0;
- XtSetArg(args[num_args], XtNtranslations,
- XtParseTranslationTable(message_translations)); num_args++;
- wp->w = XtCreateManagedWidget(
- "message", /* name */
- windowWidgetClass, /* widget class from Window.h */
- viewport, /* parent widget */
- args, /* set some values */
- num_args); /* number of values to set */
-
- XtAddCallback(wp->w, XtNexposeCallback, mesg_exposed, (XtPointer) 0);
-
- /*
- * Now adjust the height and width of the message window so that it
- * is DEFAULT_LINES_DISPLAYED high and DEFAULT_MESSAGE_WIDTH wide.
- */
-
- /* Get the font information. */
- num_args = 0;
- XtSetArg(args[num_args], XtNfont, &mesg_info->fs); num_args++;
- XtGetValues(wp->w, args, num_args);
-
- /* Save character information for fast use later. */
- mesg_info->char_width = mesg_info->fs->max_bounds.width;
- mesg_info->char_height = mesg_info->fs->max_bounds.ascent +
- mesg_info->fs->max_bounds.descent;
- mesg_info->char_ascent = mesg_info->fs->max_bounds.ascent;
- mesg_info->char_lbearing = -mesg_info->fs->min_bounds.lbearing;
-
- get_gc(wp->w, mesg_info);
-
- wp->pixel_height = DEFAULT_LINES_DISPLAYED * mesg_info->char_height;
-
- /* If a variable spaced font, only use 2/3 of the default size */
- if (mesg_info->fs->min_bounds.width != mesg_info->fs->max_bounds.width) {
- wp->pixel_width = ((2*DEFAULT_MESSAGE_WIDTH)/3) *
- mesg_info->fs->max_bounds.width;
- } else
- wp->pixel_width = (DEFAULT_MESSAGE_WIDTH *
- mesg_info->fs->max_bounds.width);
-
- /* Set the new width and height. */
- num_args = 0;
- XtSetArg(args[num_args], XtNwidth, wp->pixel_width); num_args++;
- XtSetArg(args[num_args], XtNheight, wp->pixel_height); num_args++;
- XtSetValues(wp->w, args, num_args);
-
- XtAddEventHandler(wp->w, KeyPressMask, False,
- (XtEventHandler) msgkey, (XtPointer) 0);
- XtAddCallback(wp->w, XtNresizeCallback, mesg_resized, (XtPointer) 0);
-
- /*
- * If we have created our own popup, then realize it so that the
- * viewport is also realized. Then resize the mesg window.
- */
- if (create_popup) {
- XtRealizeWidget(wp->popup);
- set_message_height(wp, (int) flags.msg_history);
- }
- }
-
-
- void
- destroy_message_window(wp)
- struct xwindow *wp;
- {
- if (wp->popup) {
- nh_XtPopdown(wp->popup);
- XtDestroyWidget(wp->popup);
- set_circle_buf(wp->mesg_information, 0); /* free buffer list */
- free((char *)wp->mesg_information);
- }
- wp->type = NHW_NONE;
- }
-
-
- /* Redraw message window if new lines have been added. */
- void
- display_message_window(wp)
- struct xwindow *wp;
- {
- if (wp->mesg_information->dirty) redraw_message_window(wp);
- }
-
-
- /*
- * Append a line of text to the message window. Split the line if the
- * rendering of the text is too long for the window.
- */
- void
- append_message(wp, str)
- struct xwindow *wp;
- const char *str;
- {
- char *mark, *remainder, buf[BUFSZ];
-
- if (!str) return;
-
- Strcpy(buf, str); /* we might mark it up */
-
- remainder = buf;
- do {
- mark = remainder;
- remainder = split(mark, wp->mesg_information->fs, wp->pixel_width);
- add_line(wp->mesg_information, mark);
- } while (remainder);
- }
-
- /* private functions ======================================================= */
-
- /*
- * Return the element in the circular linked list just before the given
- * element.
- */
- static struct line_element *
- get_previous(mark)
- struct line_element *mark;
- {
- struct line_element *curr;
-
- if (!mark) return (struct line_element *) 0;
-
- for (curr = mark; curr->next != mark; curr = curr->next)
- ;
- return curr;
- }
-
-
- /*
- * Set the information buffer size to count lines. We do this by creating
- * a circular linked list of elements, each of which represents a line of
- * text. New buffers are created as needed, old ones are freed if they
- * are no longer used.
- */
- static void
- set_circle_buf(mesg_info, count)
- struct mesg_info_t *mesg_info;
- int count;
- {
- int i;
- struct line_element *tail, *curr, *head;
-
- if (count < 0) panic("set_circle_buf: bad count [= %d]", count);
- if (count == mesg_info->num_lines) return; /* no change in size */
-
- if (count < mesg_info->num_lines) {
- /*
- * Toss num_lines - count line entries from our circular list.
- *
- * We lose lines from the front (top) of the list. We _know_
- * the list is non_empty.
- */
- tail = get_previous(mesg_info->head);
- for (i = mesg_info->num_lines - count; i--; ) {
- curr = mesg_info->head;
- mesg_info->head = curr->next;
- if (curr->line) free(curr->line);
- free((genericptr_t)curr);
- }
- if (count == 0) {
- /* make sure we don't have a dangling pointer */
- mesg_info->head = (struct line_element *) 0;
- } else {
- tail->next = mesg_info->head; /* link the tail to the head */
- }
- } else {
- /*
- * Add count - num_lines blank lines to the head of the list.
- *
- * Create a separate list, keeping track of the tail.
- */
- for (head = tail = 0, i = 0; i < count - mesg_info->num_lines; i++) {
- curr = (struct line_element *) alloc(sizeof(struct line_element));
- curr->line = 0;
- curr->buf_length = 0;
- curr->str_length = 0;
- if (tail) {
- tail->next = curr;
- tail = curr;
- } else {
- head = tail = curr;
- }
- }
- /*
- * Complete the circle by making the new tail point to the old head
- * and the old tail point to the new head. If our line count was
- * zero, then make the new list circular.
- */
- if (mesg_info->num_lines) {
- curr = get_previous(mesg_info->head);/* get end of old list */
-
- tail->next = mesg_info->head; /* new tail -> old head */
- curr->next = head; /* old tail -> new head */
- } else {
- tail->next = head;
- }
- mesg_info->head = head;
- }
-
- mesg_info->num_lines = count;
- /* Erase the line on a resize. */
- mesg_info->last_pause = (struct line_element *) 0;
- }
-
-
- /*
- * Make sure the given string is shorter than the given pixel width. If
- * not, back up from the end by words until we find a place to split.
- */
- static char *
- split(s, fs, pixel_width)
- char *s;
- XFontStruct *fs; /* Font for the window. */
- Dimension pixel_width;
- {
- char save, *end, *remainder;
-
- save = '\0';
- remainder = 0;
- end = eos(s); /* point to null at end of string */
-
- /* assume that if end == s, XXXXXX returns 0) */
- while ((Dimension) XTextWidth(fs, s, (int) strlen(s)) > pixel_width) {
- *end-- = save;
- while (*end != ' ') {
- if (end == s) panic("split: eos!");
- --end;
- }
- save = *end;
- *end = '\0';
- remainder = end + 1;
- }
- return remainder;
- }
-
- /*
- * Add a line of text to the window. The first line in the curcular list
- * becomes the last. So all we have to do is copy the new line over the
- * old one. If the line buffer is too small, then allocate a new, larger
- * one.
- */
- static void
- add_line(mesg_info, s)
- struct mesg_info_t *mesg_info;
- const char *s;
- {
- register struct line_element *curr = mesg_info->head;
- register int new_line_length = strlen(s);
-
- if (new_line_length + 1 > curr->buf_length) {
- if (curr->line) free(curr->line); /* free old line */
-
- curr->buf_length = new_line_length + 1;
- curr->line = (char *) alloc(curr->buf_length);
- }
-
- Strcpy(curr->line, s); /* copy info */
- curr->str_length = new_line_length; /* save string length */
-
- mesg_info->head = mesg_info->head->next; /* move head to next line */
- mesg_info->dirty = True; /* we have undrawn lines */
- }
-
-
- /*
- * Save a position in the text buffer so we can draw a line to seperate
- * text from the last time this function was called.
- *
- * Save the head position, since it is the line "after" the last displayed
- * line in the message window. The window redraw routine will draw a
- * line above this saved pointer.
- */
- void
- set_last_pause(wp)
- struct xwindow *wp;
- {
- register struct mesg_info_t *mesg_info = wp->mesg_information;
-
- #ifdef ERASE_LINE
- /*
- * If we've erased the pause line and haven't added any new lines,
- * don't try to erase the line again.
- */
- if (!mesg_info->last_pause
- && mesg_info->last_pause_head == mesg_info->head)
- return;
-
- if (mesg_info->last_pause == mesg_info->head) {
- /* No new messages in last turn. Redraw window to erase line. */
- mesg_info->last_pause = (struct line_element *) 0;
- mesg_info->last_pause_head = mesg_info->head;
- redraw_message_window(wp);
- } else {
- #endif
- mesg_info->last_pause = mesg_info->head;
- #ifdef ERASE_LINE
- }
- #endif
- }
-
-
- static void
- redraw_message_window(wp)
- struct xwindow *wp;
- {
- struct mesg_info_t *mesg_info = wp->mesg_information;
- register struct line_element *curr;
- register int row;
-
- /*
- * Do this the cheap and easy way. Clear the window and just redraw
- * the whole thing.
- *
- * This could be done more effecently with one call to XDrawText() instead
- * of many calls to XDrawString(). Maybe later.
- */
- XClearWindow(XtDisplay(wp->w), XtWindow(wp->w));
-
- /* For now, just update the whole shootn' match. */
- for (row = 0, curr = mesg_info->head;
- row < mesg_info->num_lines; row++, curr = curr->next) {
-
- register int y_base = row * mesg_info->char_height;
-
- XDrawString(XtDisplay(wp->w), XtWindow(wp->w),
- mesg_info->gc,
- mesg_info->char_lbearing,
- mesg_info->char_ascent + y_base,
- curr->line,
- curr->str_length);
-
- /*
- * This draws a line at the _top_ of the line of text pointed to by
- * mesg_info->last_pause.
- */
- if (appResources.message_line && curr == mesg_info->last_pause) {
- XDrawLine(XtDisplay(wp->w), XtWindow(wp->w),
- mesg_info->gc,
- 0, y_base, wp->pixel_width, y_base);
- }
- }
-
- mesg_info->dirty = False;
- }
-
-
- /*
- * Check the size of the viewport. If it has shrunk, then we want to
- * move the vertical slider to the bottom.
- */
- static void
- mesg_check_size_change(wp)
- struct xwindow *wp;
- {
- struct mesg_info_t *mesg_info = wp->mesg_information;
- Arg arg[2];
- Dimension new_width, new_height;
- Widget viewport;
-
- viewport = XtParent(wp->w);
-
- XtSetArg(arg[0], XtNwidth, &new_width);
- XtSetArg(arg[1], XtNheight, &new_height);
- XtGetValues(viewport, arg, TWO);
-
- /* Only move slider to bottom if new size is smaller. */
- if (new_width < mesg_info->viewport_width
- || new_height < mesg_info->viewport_height) {
- set_message_slider(wp);
- }
-
- mesg_info->viewport_width = new_width;
- mesg_info->viewport_height = new_height;
- }
-
-
- /* Event handler for message window expose events. */
- /*ARGSUSED*/
- static void
- mesg_exposed(w, event)
- Widget w;
- XExposeEvent *event; /* unused */
- {
- struct xwindow *wp;
-
- if (!XtIsRealized(w)) return;
- wp = find_widget(w);
- mesg_check_size_change(wp);
- redraw_message_window(wp);
- }
-
-
- static void
- get_gc(w, mesg_info)
- Widget w;
- struct mesg_info_t *mesg_info;
- {
- XGCValues values;
- XtGCMask mask = GCFunction | GCForeground | GCBackground | GCFont;
- Pixel fgpixel, bgpixel;
- Arg arg[2];
-
- XtSetArg(arg[0], XtNforeground, &fgpixel);
- XtSetArg(arg[1], XtNbackground, &bgpixel);
- XtGetValues(w, arg, TWO);
-
- values.foreground = fgpixel;
- values.background = bgpixel;
- values.function = GXcopy;
- values.font = WindowFont(w);
- mesg_info->gc = XtGetGC(w, mask, &values);
- }
-
- /*
- * Handle resizes on a message window. Correct saved pixel height and width.
- * Adjust circle buffer to accomidate the new size.
- *
- * Problem: If the resize decreases the width of the window such that
- * some lines are now longer than the window, they will be cut off by
- * X itself. All new lines will be split to the new size, but the ends
- * of the old ones will not be seen again unless the window is lengthened.
- * I don't deal with this problem because it isn't worth the trouble.
- */
- /* ARGSUSED */
- static void
- mesg_resized(w, client_data, call_data)
- Widget w;
- XtPointer call_data, client_data;
- {
- Arg args[4];
- Cardinal num_args;
- Dimension pixel_width, pixel_height;
- struct xwindow *wp;
- #ifdef VERBOSE
- int old_lines;
-
- old_lines = wp->mesg_information->num_lines;;
- #endif
-
- num_args = 0;
- XtSetArg(args[num_args], XtNwidth, &pixel_width); num_args++;
- XtSetArg(args[num_args], XtNheight, &pixel_height); num_args++;
- XtGetValues(w, args, num_args);
-
- wp = find_widget(w);
- wp->pixel_width = pixel_width;
- wp->pixel_height = pixel_height;
-
- set_circle_buf(wp->mesg_information,
- (int) pixel_height / wp->mesg_information->char_height);
-
- #ifdef VERBOSE
- printf("Message resize. Pixel: width = %d, height = %d; Lines: old = %d, new = %d\n",
- pixel_width,
- pixel_height,
- old_lines,
- wp->mesg_information->num_lines);
- #endif
- }
-